/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2007-2010 Sun Microsystems, Inc.
 * Portions Copyright 2015-2016 ForgeRock AS.
 */
package org.forgerock.opendj.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.DefinitionDecodingException.Reason;

/**
 * Defines the structure of an abstract managed object. Abstract managed objects
 * cannot be instantiated.
 * <p>
 * Applications can query a managed object definition in order to determine the
 * overall configuration model of an application.
 *
 * @param <C>
 *            The type of client managed object configuration that this
 *            definition represents.
 * @param <S>
 *            The type of server managed object configuration that this
 *            definition represents.
 */
public abstract class AbstractManagedObjectDefinition<C extends ConfigurationClient, S extends Configuration> {

    /** The name of the definition. */
    private final String name;

    /** The parent managed object definition if applicable. */
    private final AbstractManagedObjectDefinition<? super C, ? super S> parent;

    /** The set of constraints associated with this managed object definition. */
    private final Collection<Constraint> constraints = new LinkedList<>();
    /** The set of property definitions applicable to this managed object definition. */
    private final Map<String, PropertyDefinition<?>> propertyDefinitions = new HashMap<>();
    /** The set of relation definitions applicable to this managed object definition. */
    private final Map<String, RelationDefinition<?, ?>> relationDefinitions = new HashMap<>();
    /** The set of relation definitions directly referencing this managed object definition. */
    private final Set<RelationDefinition<C, S>> reverseRelationDefinitions = new HashSet<>();

    /**
     * The set of all property definitions associated with this managed
     * object definition including inherited property definitions.
     */
    private final Map<String, PropertyDefinition<?>> allPropertyDefinitions = new HashMap<>();
    /**
     * The set of all relation definitions associated with this managed
     * object definition including inherited relation definitions.
     */
    private final Map<String, RelationDefinition<?, ?>> allRelationDefinitions = new HashMap<>();

    /** The set of aggregation property definitions applicable to this managed object definition. */
    private final Map<String, AggregationPropertyDefinition<?, ?>> aggregationPropertyDefinitions = new HashMap<>();

    /** The set of aggregation property definitions directly referencing this managed object definition. */
    private final Vector<AggregationPropertyDefinition<?, ?>> reverseAggregationPropertyDefinitions = new Vector<>();

    /**
     * The set of all aggregation property definitions associated with this
     * managed object definition including inherited relation definitions.
     */
    private final Map<String, AggregationPropertyDefinition<?, ?>> allAggregationPropertyDefinitions = new HashMap<>();

    /** The set of tags associated with this managed object. */
    private final Set<Tag> allTags = new HashSet<>();

    /** Options applicable to this definition. */
    private final Set<ManagedObjectOption> options = EnumSet.noneOf(ManagedObjectOption.class);

    /** The set of managed object definitions which inherit from this definition. */
    private final Map<String, AbstractManagedObjectDefinition<? extends C, ? extends S>> children = new TreeMap<>();

    /**
     * Create a new abstract managed object definition.
     *
     * @param name
     *            The name of the definition.
     * @param parent
     *            The parent definition, or <code>null</code> if there is no
     *            parent (only the {@link TopCfgDefn} should have a
     *            <code>null</code> parent, unless the definition is being used
     *            for testing).
     */
    protected AbstractManagedObjectDefinition(String name,
        AbstractManagedObjectDefinition<? super C, ? super S> parent) {
        this.name = name;
        this.parent = parent;

        // If we have a parent definition then inherit its features.
        if (parent != null) {
            registerInParent();

            for (PropertyDefinition<?> pd : parent.getAllPropertyDefinitions()) {
                allPropertyDefinitions.put(pd.getName(), pd);
            }

            for (RelationDefinition<?, ?> rd : parent.getAllRelationDefinitions()) {
                allRelationDefinitions.put(rd.getName(), rd);
            }

            for (AggregationPropertyDefinition<?, ?> apd : parent.getAllAggregationPropertyDefinitions()) {
                allAggregationPropertyDefinitions.put(apd.getName(), apd);
            }
            // Tag inheritance is performed during preprocessing.
        }
    }

    /**
     * Get all the child managed object definitions which inherit from this
     * managed object definition.
     *
     * @return Returns an unmodifiable collection containing all the subordinate
     *         managed object definitions which inherit from this managed object
     *         definition.
     */
    public final Collection<AbstractManagedObjectDefinition<? extends C, ? extends S>> getAllChildren() {
        List<AbstractManagedObjectDefinition<? extends C, ? extends S>> list = new ArrayList<>(children.values());

        for (AbstractManagedObjectDefinition<? extends C, ? extends S> child : children.values()) {
            list.addAll(child.getAllChildren());
        }

        return Collections.unmodifiableCollection(list);
    }

    /**
     * Get all the constraints associated with this type of managed object. The
     * returned collection will contain inherited constraints.
     *
     * @return Returns a collection containing all the constraints associated
     *         with this type of managed object. The caller is free to modify
     *         the collection if required.
     */
    public final Collection<Constraint> getAllConstraints() {
        // This method does not used a cached set of constraints because
        // constraints may be updated after child definitions have been defined.
        List<Constraint> allConstraints = new LinkedList<>();

        if (parent != null) {
            allConstraints.addAll(parent.getAllConstraints());
        }
        allConstraints.addAll(constraints);

        return allConstraints;
    }

    /**
     * Get all the property definitions associated with this type of managed
     * object. The returned collection will contain inherited property
     * definitions.
     *
     * @return Returns an unmodifiable collection containing all the property
     *         definitions associated with this type of managed object.
     */
    public final Collection<PropertyDefinition<?>> getAllPropertyDefinitions() {
        return Collections.unmodifiableCollection(allPropertyDefinitions.values());
    }

    /**
     * Get all the relation definitions associated with this type of managed
     * object. The returned collection will contain inherited relation
     * definitions.
     *
     * @return Returns an unmodifiable collection containing all the relation
     *         definitions associated with this type of managed object.
     */
    public final Collection<RelationDefinition<?, ?>> getAllRelationDefinitions() {
        return Collections.unmodifiableCollection(allRelationDefinitions.values());
    }

    /**
     * Get all the relation definitions which refer to this managed object
     * definition. The returned collection will contain relation definitions
     * which refer to parents of this managed object definition.
     *
     * @return Returns a collection containing all the relation definitions
     *         which refer to this managed object definition. The caller is free
     *         to modify the collection if required.
     */
    public final Collection<RelationDefinition<? super C, ? super S>> getAllReverseRelationDefinitions() {
        // This method does not used a cached set of relations because
        // relations may be updated after child definitions have been defined.
        List<RelationDefinition<? super C, ? super S>> rdlist = new LinkedList<>();

        if (parent != null) {
            rdlist.addAll(parent.getAllReverseRelationDefinitions());
        }
        rdlist.addAll(reverseRelationDefinitions);

        return rdlist;
    }

    /**
     * Get all the aggregation property definitions associated with this type of
     * managed object. The returned collection will contain inherited
     * aggregation property definitions.
     *
     * @return Returns an unmodifiable collection containing all the aggregation
     *         property definitions associated with this type of managed object.
     */
    public final Collection<AggregationPropertyDefinition<?, ?>> getAllAggregationPropertyDefinitions() {
        return Collections.unmodifiableCollection(allAggregationPropertyDefinitions.values());
    }

    /**
     * Get all the aggregation property definitions which refer to this managed
     * object definition. The returned collection will contain aggregation
     * property definitions which refer to parents of this managed object
     * definition.
     *
     * @return Returns a collection containing all the aggregation property
     *         definitions which refer to this managed object definition. The
     *         caller is free to modify the collection if required.
     */
    public final Collection<AggregationPropertyDefinition<?, ?>> getAllReverseAggregationPropertyDefinitions() {
        // This method does not used a cached set of aggregation properties because
        // aggregation properties may be updated after child definitions have been defined.
        List<AggregationPropertyDefinition<?, ?>> apdlist = new LinkedList<>();

        if (parent != null) {
            apdlist.addAll(parent.getAllReverseAggregationPropertyDefinitions());
        }
        apdlist.addAll(reverseAggregationPropertyDefinitions);

        return apdlist;
    }

    /**
     * Get all the tags associated with this type of managed object. The
     * returned collection will contain inherited tags.
     *
     * @return Returns an unmodifiable collection containing all the tags
     *         associated with this type of managed object.
     */
    public final Collection<Tag> getAllTags() {
        return Collections.unmodifiableCollection(allTags);
    }

    /**
     * Get the named child managed object definition which inherits from this
     * managed object definition. This method will recursively search down
     * through the inheritance hierarchy.
     *
     * @param name
     *            The name of the managed object definition sub-type.
     * @return Returns the named child managed object definition which inherits
     *         from this managed object definition.
     * @throws IllegalArgumentException
     *             If the specified managed object definition name was null or
     *             empty or if the requested subordinate managed object
     *             definition was not found.
     */
    public final AbstractManagedObjectDefinition<? extends C, ? extends S> getChild(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("null or empty managed object name");
        }

        AbstractManagedObjectDefinition<? extends C, ? extends S> d = children.get(name);

        if (d == null) {
            // Recursively search.
            for (AbstractManagedObjectDefinition<? extends C, ? extends S> child : children.values()) {
                try {
                    d = child.getChild(name);
                    break;
                } catch (IllegalArgumentException e) {
                    // Try the next child.
                }
            }
        }

        if (d == null) {
            throw new IllegalArgumentException("child managed object definition \"" + name + "\" not found");
        }

        return d;
    }

    /**
     * Get the child managed object definitions which inherit directly from this
     * managed object definition.
     *
     * @return Returns an unmodifiable collection containing the subordinate
     *         managed object definitions which inherit directly from this
     *         managed object definition.
     */
    public final Collection<AbstractManagedObjectDefinition<? extends C, ? extends S>> getChildren() {
        return Collections.unmodifiableCollection(children.values());
    }

    /**
     * Get the constraints defined by this managed object definition. The
     * returned collection will not contain inherited constraints.
     *
     * @return Returns an unmodifiable collection containing the constraints
     *         defined by this managed object definition.
     */
    public final Collection<Constraint> getConstraints() {
        return Collections.unmodifiableCollection(constraints);
    }

    /**
     * Gets the optional description of this managed object definition in the
     * default locale.
     *
     * @return Returns the description of this managed object definition in the
     *         default locale, or <code>null</code> if there is no description.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getDescription() {
        return getDescription(Locale.getDefault());
    }

    /**
     * Gets the optional description of this managed object definition in the
     * specified locale.
     *
     * @param locale
     *            The locale.
     * @return Returns the description of this managed object definition in the
     *         specified locale, or <code>null</code> if there is no
     *         description.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getDescription(Locale locale) {
        try {
            return ManagedObjectDefinitionI18NResource.getInstance().getMessage(this, "description", locale);
        } catch (MissingResourceException e) {
            return null;
        }
    }

    /**
     * Get the name of the definition.
     *
     * @return Returns the name of the definition.
     */
    public final String getName() {
        return name;
    }

    /**
     * Get the parent managed object definition, if applicable.
     *
     * @return Returns the parent of this managed object definition, or
     *         <code>null</code> if this definition is the {@link TopCfgDefn}.
     */
    public final AbstractManagedObjectDefinition<? super C, ? super S> getParent() {
        return parent;
    }

    /**
     * Get the specified property definition associated with this type of
     * managed object. The search will include any inherited property
     * definitions.
     *
     * @param name
     *            The name of the property definition to be retrieved.
     * @return Returns the specified property definition associated with this
     *         type of managed object.
     * @throws IllegalArgumentException
     *             If the specified property name was null or empty or if the
     *             requested property definition was not found.
     */
    public final PropertyDefinition<?> getPropertyDefinition(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("null or empty property name");
        }

        PropertyDefinition<?> d = allPropertyDefinitions.get(name);
        if (d == null) {
            throw new IllegalArgumentException("property definition \"" + name + "\" not found");
        }

        return d;
    }

    /**
     * Get the property definitions defined by this managed object definition.
     * The returned collection will not contain inherited property definitions.
     *
     * @return Returns an unmodifiable collection containing the property
     *         definitions defined by this managed object definition.
     */
    public final Collection<PropertyDefinition<?>> getPropertyDefinitions() {
        return Collections.unmodifiableCollection(propertyDefinitions.values());
    }

    /**
     * Get the specified relation definition associated with this type of
     * managed object.The search will include any inherited relation
     * definitions.
     *
     * @param name
     *            The name of the relation definition to be retrieved.
     * @return Returns the specified relation definition associated with this
     *         type of managed object.
     * @throws IllegalArgumentException
     *             If the specified relation name was null or empty or if the
     *             requested relation definition was not found.
     */
    public final RelationDefinition<?, ?> getRelationDefinition(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("null or empty relation name");
        }

        RelationDefinition<?, ?> d = allRelationDefinitions.get(name);
        if (d == null) {
            throw new IllegalArgumentException("relation definition \"" + name + "\" not found");
        }

        return d;
    }

    /**
     * Get the relation definitions defined by this managed object definition.
     * The returned collection will not contain inherited relation definitions.
     *
     * @return Returns an unmodifiable collection containing the relation
     *         definitions defined by this managed object definition.
     */
    public final Collection<RelationDefinition<?, ?>> getRelationDefinitions() {
        return Collections.unmodifiableCollection(relationDefinitions.values());
    }

    /**
     * Get the relation definitions which refer directly to this managed object
     * definition. The returned collection will not contain relation definitions
     * which refer to parents of this managed object definition.
     *
     * @return Returns an unmodifiable collection containing the relation
     *         definitions which refer directly to this managed object
     *         definition.
     */
    public final Collection<RelationDefinition<C, S>> getReverseRelationDefinitions() {
        return Collections.unmodifiableCollection(reverseRelationDefinitions);
    }

    /**
     * Get the specified aggregation property definition associated with this
     * type of managed object.The search will include any inherited aggregation
     * property definitions.
     *
     * @param name
     *            The name of the aggregation property definition to be
     *            retrieved.
     * @return Returns the specified aggregation property definition associated
     *         with this type of managed object.
     * @throws IllegalArgumentException
     *             If the specified aggregation property name was null or empty
     *             or if the requested aggregation property definition was not
     *             found.
     */
    public final AggregationPropertyDefinition<?, ?> getAggregationPropertyDefinition(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("null or empty aggregation property name");
        }

        AggregationPropertyDefinition<?, ?> d = allAggregationPropertyDefinitions.get(name);
        if (d == null) {
            throw new IllegalArgumentException("aggregation property definition \"" + name + "\" not found");
        }

        return d;
    }

    /**
     * Get the aggregation property definitions defined by this managed object
     * definition. The returned collection will not contain inherited
     * aggregation property definitions.
     *
     * @return Returns an unmodifiable collection containing the aggregation
     *         property definitions defined by this managed object definition.
     */
    public final Collection<AggregationPropertyDefinition<?, ?>> getAggregationPropertyDefinitions() {
        return Collections.unmodifiableCollection(aggregationPropertyDefinitions.values());
    }

    /**
     * Get the aggregation property definitions which refer directly to this
     * managed object definition. The returned collection will not contain
     * aggregation property definitions which refer to parents of this managed
     * object definition.
     *
     * @return Returns an unmodifiable collection containing the aggregation
     *         property definitions which refer directly to this managed object
     *         definition.
     */
    public final Collection<AggregationPropertyDefinition<?, ?>> getReverseAggregationPropertyDefinitions() {
        return Collections.unmodifiableCollection(reverseAggregationPropertyDefinitions);
    }

    /**
     * Gets the synopsis of this managed object definition in the default
     * locale.
     *
     * @return Returns the synopsis of this managed object definition in the
     *         default locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getSynopsis() {
        return getSynopsis(Locale.getDefault());
    }

    /**
     * Gets the synopsis of this managed object definition in the specified
     * locale.
     *
     * @param locale
     *            The locale.
     * @return Returns the synopsis of this managed object definition in the
     *         specified locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getSynopsis(Locale locale) {
        return ManagedObjectDefinitionI18NResource.getInstance().getMessage(this, "synopsis", locale);
    }

    /**
     * Gets the user friendly name of this managed object definition in the
     * default locale.
     *
     * @return Returns the user friendly name of this managed object definition
     *         in the default locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getUserFriendlyName() {
        return getUserFriendlyName(Locale.getDefault());
    }

    /**
     * Gets the user friendly name of this managed object definition in the
     * specified locale.
     *
     * @param locale
     *            The locale.
     * @return Returns the user friendly name of this managed object definition
     *         in the specified locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getUserFriendlyName(Locale locale) {
        return LocalizableMessage.raw(ManagedObjectDefinitionI18NResource.getInstance().getMessage(this,
            "user-friendly-name", locale));
    }

    /**
     * Gets the user friendly plural name of this managed object definition in
     * the default locale.
     *
     * @return Returns the user friendly plural name of this managed object
     *         definition in the default locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getUserFriendlyPluralName() {
        return getUserFriendlyPluralName(Locale.getDefault());
    }

    /**
     * Gets the user friendly plural name of this managed object definition in
     * the specified locale.
     *
     * @param locale
     *            The locale.
     * @return Returns the user friendly plural name of this managed object
     *         definition in the specified locale.
     * @throws UnsupportedOperationException
     *             If this managed object definition is the {@link TopCfgDefn}.
     */
    public final LocalizableMessage getUserFriendlyPluralName(Locale locale) {
        return ManagedObjectDefinitionI18NResource.getInstance()
            .getMessage(this, "user-friendly-plural-name", locale);
    }

    /**
     * Determine whether there are any child managed object definitions which
     * inherit from this managed object definition.
     *
     * @return Returns <code>true</code> if this type of managed object has any
     *         child managed object definitions, <code>false</code> otherwise.
     */
    public final boolean hasChildren() {
        return !children.isEmpty();
    }

    /**
     * Determines whether this managed object definition has the
     * specified option.
     *
     * @param option
     *            The option to test.
     * @return Returns <code>true</code> if the option is set, or
     *         <code>false</code> otherwise.
     */
    public final boolean hasOption(ManagedObjectOption option) {
        return options.contains(option);
    }

    /**
     * Determines whether this managed object definition has the
     * specified tag.
     *
     * @param t
     *            The tag definition.
     * @return Returns <code>true</code> if this managed object definition has
     *         the specified tag.
     */
    public final boolean hasTag(Tag t) {
        return allTags.contains(t);
    }

    /**
     * Determines whether this managed object definition is a sub-type of
     * the provided managed object definition. This managed object definition is
     * a sub-type of the provided managed object definition if they are both the
     * same or if the provided managed object definition can be obtained by
     * recursive invocations of the {@link #getParent()} method.
     *
     * @param d
     *            The managed object definition to be checked.
     * @return Returns <code>true</code> if this managed object definition is a
     *         sub-type of the provided managed object definition.
     */
    public final boolean isChildOf(AbstractManagedObjectDefinition<?, ?> d) {
        AbstractManagedObjectDefinition<?, ?> i;
        for (i = this; i != null; i = i.parent) {
            if (i == d) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines whether this managed object definition is a super-type
     * of the provided managed object definition. This managed object definition
     * is a super-type of the provided managed object definition if they are
     * both the same or if the provided managed object definition is a member of
     * the set of children returned from {@link #getAllChildren()}.
     *
     * @param d
     *            The managed object definition to be checked.
     * @return Returns <code>true</code> if this managed object definition is a
     *         super-type of the provided managed object definition.
     */
    public final boolean isParentOf(AbstractManagedObjectDefinition<?, ?> d) {
        return d.isChildOf(this);
    }

    /**
     * Determines whether this managed object definition is the {@link TopCfgDefn}.
     *
     * @return Returns <code>true</code> if this managed object definition is
     *         the {@link TopCfgDefn}.
     */
    public final boolean isTop() {
        return this instanceof TopCfgDefn;
    }

    /**
     * Finds a sub-type of this managed object definition which most closely
     * corresponds to the matching criteria of the provided definition resolver.
     *
     * @param r
     *            The definition resolver.
     * @return Returns the sub-type of this managed object definition which most
     *         closely corresponds to the matching criteria of the provided
     *         definition resolver.
     * @throws DefinitionDecodingException
     *             If no matching sub-type could be found or if the resolved
     *             definition was abstract.
     * @see DefinitionResolver
     */
    public final ManagedObjectDefinition<? extends C, ? extends S> resolveManagedObjectDefinition(DefinitionResolver r)
            throws DefinitionDecodingException {
        AbstractManagedObjectDefinition<? extends C, ? extends S> rd;
        rd = resolveManagedObjectDefinitionAux(this, r);
        if (rd == null) {
            // Unable to resolve the definition.
            throw new DefinitionDecodingException(this, Reason.WRONG_TYPE_INFORMATION);
        } else if (rd instanceof ManagedObjectDefinition) {
            return (ManagedObjectDefinition<? extends C, ? extends S>) rd;
        } else {
            // Resolved definition was abstract.
            throw new DefinitionDecodingException(this, Reason.ABSTRACT_TYPE_INFORMATION);
        }
    }

    @Override
    public final String toString() {
        StringBuilder builder = new StringBuilder();
        toString(builder);
        return builder.toString();
    }

    /**
     * Append a string representation of the managed object definition to the
     * provided string builder.
     *
     * @param builder
     *            The string builder where the string representation should be
     *            appended.
     */
    public final void toString(StringBuilder builder) {
        builder.append(getName());
    }

    /**
     * Initializes all of the components associated with this managed object
     * definition.
     *
     * @throws Exception
     *             If this managed object definition could not be initialized.
     */
    protected final void initialize() throws Exception {
        for (PropertyDefinition<?> pd : getAllPropertyDefinitions()) {
            pd.initialize();
            pd.getDefaultBehaviorProvider().initialize();
        }

        for (RelationDefinition<?, ?> rd : getAllRelationDefinitions()) {
            rd.initialize();
        }

        for (AggregationPropertyDefinition<?, ?> apd : getAllAggregationPropertyDefinitions()) {

            apd.initialize();
            /*
             * Now register the aggregation property in the referenced managed
             * object definition for reverse lookups.
             */
            registerReverseAggregationPropertyDefinition(apd);
        }

        for (Constraint constraint : getAllConstraints()) {
            constraint.initialize();
        }
    }

    /**
     * Register a constraint with this managed object definition.
     * <p>
     * This method <b>must not</b> be called by applications.
     *
     * @param constraint
     *            The constraint to be registered.
     */
    protected final void registerConstraint(Constraint constraint) {
        constraints.add(constraint);
    }

    /**
     * Register a property definition with this managed object definition,
     * overriding any existing property definition with the same name.
     * <p>
     * This method <b>must not</b> be called by applications.
     *
     * @param d
     *            The property definition to be registered.
     */
    protected final void registerPropertyDefinition(PropertyDefinition<?> d) {
        String propName = d.getName();

        propertyDefinitions.put(propName, d);
        allPropertyDefinitions.put(propName, d);

        if (d instanceof AggregationPropertyDefinition<?, ?>) {
            AggregationPropertyDefinition<?, ?> apd = (AggregationPropertyDefinition<?, ?>) d;
            aggregationPropertyDefinitions.put(propName, apd);
            // The key must also contain the managed object name, since several
            // MOs
            // in an inheritance tree may aggregate the same aggregation
            // property name
            allAggregationPropertyDefinitions.put(apd.getManagedObjectDefinition().getName() + ":" + propName, apd);
        }
    }

    /**
     * Register a relation definition with this managed object definition,
     * overriding any existing relation definition with the same name.
     * <p>
     * This method <b>must not</b> be called by applications.
     *
     * @param d
     *            The relation definition to be registered.
     */
    protected final void registerRelationDefinition(RelationDefinition<?, ?> d) {
        // Register the relation in this managed object definition.
        String relName = d.getName();

        relationDefinitions.put(relName, d);
        allRelationDefinitions.put(relName, d);

        // Now register the relation in the referenced managed object
        // definition for reverse lookups.
        registerReverseRelationDefinition(d);
    }

    /**
     * Register an option with this managed object definition.
     * <p>
     * This method <b>must not</b> be called by applications.
     *
     * @param option
     *            The option to be registered.
     */
    protected final void registerOption(ManagedObjectOption option) {
        options.add(option);
    }

    /**
     * Register a tag with this managed object definition.
     * <p>
     * This method <b>must not</b> be called by applications.
     *
     * @param tag
     *            The tag to be registered.
     */
    protected final void registerTag(Tag tag) {
        allTags.add(tag);
    }

    /**
     * Deregister a constraint from the managed object definition.
     * <p>
     * This method <b>must not</b> be called by applications and is only
     * intended for internal testing.
     *
     * @param constraint
     *            The constraint to be deregistered.
     */
    final void deregisterConstraint(Constraint constraint) {
        if (!constraints.remove(constraint)) {
            throw new RuntimeException("Failed to deregister a constraint");
        }
    }

    /**
     * Deregister a relation definition from the managed object definition.
     * <p>
     * This method <b>must not</b> be called by applications and is only
     * intended for internal testing.
     *
     * @param d
     *            The relation definition to be deregistered.
     */
    final void deregisterRelationDefinition(RelationDefinition<?, ?> d) {
        // Deregister the relation from this managed object definition.
        String relName = d.getName();
        relationDefinitions.remove(relName);
        allRelationDefinitions.remove(relName);

        // Now deregister the relation from the referenced managed object
        // definition for reverse lookups.
        d.getChildDefinition().reverseRelationDefinitions.remove(d);
    }

    /**
     * Register this managed object definition in its parent.
     * <p>
     * This method <b>must not</b> be called by applications and is only
     * intended for internal testing.
     */
    final void registerInParent() {
        if (parent != null) {
            parent.children.put(name, this);
        }
    }

    /**
     * Register a relation definition in the referenced managed object
     * definition's reverse lookup table.
     */
    private <C1 extends ConfigurationClient, S1 extends Configuration> void registerReverseRelationDefinition(
        RelationDefinition<C1, S1> rd) {
        rd.getChildDefinition().reverseRelationDefinitions.add(rd);
    }

    /**
     * Register a aggregation property definition in the referenced managed
     * object definition's reverse lookup table.
     */
    private void registerReverseAggregationPropertyDefinition(AggregationPropertyDefinition<?, ?> apd) {

        apd.getRelationDefinition().getChildDefinition().reverseAggregationPropertyDefinitions.add(apd);
    }

    /** Recursively descend definition hierarchy to find the best match definition. */
    private AbstractManagedObjectDefinition<? extends C, ? extends S> resolveManagedObjectDefinitionAux(
        AbstractManagedObjectDefinition<? extends C, ? extends S> d, DefinitionResolver r) {
        if (!r.matches(d)) {
            return null;
        }

        for (AbstractManagedObjectDefinition<? extends C, ? extends S> child : d.getChildren()) {
            AbstractManagedObjectDefinition<? extends C, ? extends S> rd =
                resolveManagedObjectDefinitionAux(child, r);
            if (rd != null) {
                return rd;
            }
        }

        return d;
    }
}
